<?php
declare(strict_types=1);

namespace App\Utils\Other\Office\Excel;

use App\Exception\InvalidRequestException;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Filesystem\FilesystemFactory;
use Hyperf\HttpMessage\Upload\UploadedFile;
use Hyperf\Validation\Contract\ValidatorFactoryInterface;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
use function Hyperf\Config\config;
use function Hyperf\Translation\__;
use Hyperf\Collection\Arr;
use function Hyperf\Support\make;

Class Import {

    /**
     * @var Spreadsheet
     */
    protected Spreadsheet $reader;

    #[Inject]
    protected ValidatorFactoryInterface $validatorFactory;

    /**
     * 文件类型
     * @var array|string[]
     */
    public static array $mineType = [
        'application/vnd.ms-office',
        'text/x-comma-separated-values',
        'text/comma-separated-values',
        'application/octet-stream',
        'application/vnd.ms-excel',
        'application/x-csv',
        'text/x-csv',
        'text/csv',
        'application/csv',
        'application/excel',
        'application/vnd.msexcel',
        'text/plain',
        'application/zip',
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
    ];

    /**
     * 读取文件
     * @param $file
     * @return $this
     */
    public function reader($file):static
    {
        if ($file instanceof UploadedFile) {
            $ext = $file->getExtension();
            $file = $file->getRealPath();
        }
        if (!is_file($file)) {
            throw new InvalidRequestException(__('message.file.failed'));
        }
        $ext = $ext??pathinfo($file, PATHINFO_EXTENSION);
        if ($ext === 'xlsx') {
            $reader = IOFactory::createReader('Xlsx');
            $this->reader = $reader->load($file);
        } else {
            $this->reader = IOFactory::load($file);
        }
        return $this;
    }

    /**
     * 获取表格中的数据并验证
     * @param int $index
     * @param bool $check
     * @param array|null $column
     * @param array|null $callbacks
     * @param int $startRow
     * @return array
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     */
    public function getData(int $index=0, bool $check=false, array $column=null, array $callbacks=null, int $startRow=2)
    {
        $currentSheet = $this->reader->getSheet($index);
        //获取总列数
        $allColumn = $currentSheet->getHighestColumn();
        // 获取总行数
        $allRow = $currentSheet->getHighestRow();
        //循环获取表中的数据，$currentRow表示当前行，从哪行开始读取数据，索引值从0开始
        $data = [];
        for ($currentRow = $startRow; $currentRow <= $allRow; $currentRow++) {
            for ($currentColumn = 'A'; $currentColumn <= $allColumn; $currentColumn++) {
                // 数据坐标
                $cell = $currentSheet->getCell("{$currentColumn}{$currentRow}")->getValue();
                if ($cell instanceof RichText) {
                    $cell = $cell->getPlainText();
                }
                // 读取到的数据保存到数组中
                $data[$currentRow][$currentColumn] = $cell?trim((string)$cell):$cell;
            }
        }
        // 销毁实例
        $this->reader->disconnectWorksheets();
        unset($this->reader);

        if (true === $check) {
            $this->checkRequire($data, $column, $callbacks);
        }

        return $data;
    }

    /**
     * 获取图片和数据
     * @param int $index
     * @param bool $check
     * @param array|null $column
     * @param array|null $callbacks
     * @param int $startRow
     * @return array
     * @throws \League\Flysystem\FilesystemException
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     */
    public function getImageAndData(int $index=0, bool $check=false, array $column=null, array $callbacks=null, int $startRow=2)
    {
        $currentSheet = $this->reader->getSheet($index);

        $config = config('file.storage.qiniu');
        $imageData = [];
        /** @var Drawing $drawing */
        foreach ($currentSheet->getDrawingCollection() as $drawing) {
            $stream = fopen($drawing->getPath(), 'rb');
            try {
                $streamInfo = fstat($stream);
                $size = $streamInfo['size'];
                [$startImageColumn, $startImageRow] = Coordinate::coordinateFromString($drawing->getCoordinates());
                if ($size > 512000) {
                    fclose($stream); // 关闭文件
                    throw new InvalidRequestException("Line {$startImageColumn}{$startImageRow}:". __('message.file.exceed', [
                            'size' => '500kb'
                        ]));
                }
                $microsecond = getMicrosecond();
                $key = Arr::last($microsecond).Arr::first($microsecond).'.'.$drawing->getExtension();
                /** @var FilesystemFactory $factory */
                $factory = make(FilesystemFactory::class);
                $qiniu = $factory->get('qiniu');

                $qiniu->writeStream(
                    $key,
                    $stream
                );
            } catch (\Throwable $throwable) {
                fclose($stream);
                throw $throwable;
            }
            fclose($stream);
            $imageData[$startImageRow][$startImageColumn] = "{$config['domain']}/{$key}";
        }

        $data = $this->getData(...func_get_args());

        foreach ($imageData as $row => $imageDatum) {
            foreach ($imageDatum as $column => $item) {
                $data[$row][$column] = $item;
            }
        }

        return $data;
    }

    /**
     * 判断单元格是否有值
     * @param $val
     * @return bool
     */
    public static function fillIn($val)
    {
        return $val === 0 || $val === '0' || $val;
    }

    /**
     * 检查行
     * @param array $data
     * @param array|null $column
     * @param array|null $callbacks
     * @return bool
     */
    public function checkRequire(array $data, array $column=null, array $callbacks=null):bool
    {
        foreach ($data as $rowIndex => $datum) {
            foreach ($datum as $columnIndex => $item) {
                $field = array_search($columnIndex, $column);
                if ((null === $column || in_array($columnIndex, $column)) && !static::fillIn($item)) {
                    if (null === $column || is_numeric($field)) {
                        throw new InvalidRequestException(__('message.required.row.num', [
                            'num' => $rowIndex
                        ]));
                    } else {
                        throw new InvalidRequestException(__('message.required.row.field', [
                            'num' => $rowIndex,
                            'field' => is_numeric($field)?($rowIndex.$columnIndex):$field
                        ]));
                    }
                }
                if ($callbacks && Arr::get($callbacks, $columnIndex) instanceof \Closure) {
                    $message = $callbacks[$columnIndex]($item, $datum);
                    if (true !== $message) {
                        throw new InvalidRequestException(__('message.required.row.message', [
                            'num' => $rowIndex,
                            'message' => $message
                        ]));
                    }
                }
            }
        }

        return true;
    }
}